package com.googlecode.microanalyst.designer.model;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.ui.views.properties.PropertyDescriptor;

import com.googlecode.microanalyst.designer.i18n.DesignerMessages;
import com.googlecode.microanalyst.designer.model.property.PropertyHandler;

public abstract class AnalysisElement implements Cloneable, Serializable,
		IPropertySource {

	private static final long serialVersionUID = -7039749698825443914L;

	public static final String CHILDREN = "Children", //$NON-NLS-1$
			INPUTS = "inputs", //$NON-NLS-1$
			OUTPUTS = "outputs"; //$NON-NLS-1$

	protected Dimension size = new Dimension(-1, -1);

	transient protected PropertyChangeSupport listeners;

	private AnalysisGuide horizontalGuide;

	private Map<String, AnalysisConnection> inputs = new HashMap<String, AnalysisConnection>();

	private Point location = new Point(0, 0);

	protected List<AnalysisConnection> outputs = new ArrayList<AnalysisConnection>();

	private AnalysisGuide verticalGuide;

	protected static IPropertyDescriptor[] descriptors = null;
	public static String ID_SIZE = "size"; //$NON-NLS-1$
	public static String ID_LOCATION = "location"; //$NON-NLS-1$

	static {
		descriptors = new IPropertyDescriptor[] {
				new PropertyDescriptor(ID_SIZE,
						DesignerMessages.PropertyDescriptor_Tool_Size),
				new PropertyDescriptor(ID_LOCATION,
						DesignerMessages.PropertyDescriptor_Tool_Location) };
	}

	protected static Image createImage(Class<?> rsrcClass, String name) {
		InputStream stream = rsrcClass.getResourceAsStream(name);
		Image image = new Image(null, stream);
		try {
			stream.close();
		} catch (IOException ioe) {
		}
		return image;
	}

	public void setSize(Dimension size) {
		if (this.size.equals(size))
			return;
		this.size = size;
		firePropertyChange(ID_SIZE, null, size); //$NON-NLS-1$
	}

	public Dimension getSize() {
		return size;
	}

	public void setLocation(Point p) {
		if (location.equals(p))
			return;
		location = p;
		firePropertyChange(ID_LOCATION, null, p); //$NON-NLS-1$
	}

	public Point getLocation() {
		return location;
	}

	private PropertyChangeSupport getListeners() {
		if (listeners == null) {
			listeners = new PropertyChangeSupport(this);
		}
		return listeners;
	}

	public void addPropertyChangeListener(PropertyChangeListener l) {
		getListeners().addPropertyChangeListener(l);
	}

	public void removePropertyChangeListener(PropertyChangeListener l) {
		getListeners().removePropertyChangeListener(l);
	}

	protected void firePropertyChange(String prop, Object old, Object newValue) {
		getListeners().firePropertyChange(prop, old, newValue);
	}

	protected void fireChildAdded(String prop, Object child, Object index) {
		getListeners().firePropertyChange(prop, index, child);
	}

	protected void fireChildRemoved(String prop, Object child) {
		getListeners().firePropertyChange(prop, child, null);
	}

	protected void fireStructureChange(String prop, Object child) {
		getListeners().firePropertyChange(prop, null, child);
	}

	public void update() {
	}

	public Image getIcon() {
		return getIconImage();
	}

	public abstract Image getIconImage();

	public void connectInput(AnalysisConnection w) {
		inputs.put(w.getTargetTerminal(), w);
		update();
		fireStructureChange(INPUTS, w);
	}

	public void connectOutput(AnalysisConnection w) {
		outputs.add(w);
		update();
		fireStructureChange(OUTPUTS, w);
	}

	public void disconnectInput(AnalysisConnection w) {
		inputs.remove(w.getTargetTerminal());
		update();
		fireStructureChange(INPUTS, w);
	}

	public void disconnectOutput(AnalysisConnection w) {
		outputs.remove(w);
		update();
		fireStructureChange(OUTPUTS, w);
	}

	public List<AnalysisConnection> getConnections() {
		List<AnalysisConnection> conns = getSourceConnections();
		conns.addAll(getTargetConnections());
		return conns;
	}

	public AnalysisGuide getHorizontalGuide() {
		return horizontalGuide;
	}

	public List<AnalysisConnection> getSourceConnections() {
		return new ArrayList<AnalysisConnection>(outputs);
	}

	public List<AnalysisConnection> getTargetConnections() {
		return new ArrayList<AnalysisConnection>(inputs.values());
	}

	public AnalysisGuide getVerticalGuide() {
		return verticalGuide;
	}

	public void setHorizontalGuide(AnalysisGuide horizontalGuide) {
		this.horizontalGuide = horizontalGuide;
	}

	public void setVerticalGuide(AnalysisGuide verticalGuide) {
		this.verticalGuide = verticalGuide;
	}

	/**
	 * Returns useful property descriptors for the use in property sheets. this
	 * supports location and size.
	 * 
	 * @return Array of property descriptors.
	 */
	public IPropertyDescriptor[] getPropertyDescriptors() {
		PropertyHandler propertyHandler = getPropertyHandler();
		if (propertyHandler != null)
			return propertyHandler.getPropertyDescriptors();
		return new PropertyDescriptor[0];
	}

	/**
	 * Returns an Object which represents the appropriate value for the property
	 * name supplied.
	 * 
	 * @param propName
	 *            Name of the property for which the the values are needed.
	 * @return Object which is the value of the property.
	 */
	public Object getPropertyValue(Object propName) {
		if (ID_SIZE.equals(propName)) {
			Dimension s = getSize();
			return new String("(" + s.width + "," + s.height + ")");//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$
		}
		if (ID_LOCATION.equals(propName)) {
			Point p = getLocation();
			return new String("(" + p.x + "," + p.y + ")");//$NON-NLS-3$//$NON-NLS-2$//$NON-NLS-1$
		}
		PropertyHandler propertyHandler = getPropertyHandler();
		if (propertyHandler != null)
			return propertyHandler.getPropertyValue(propName + "");
		return null;
	}

	/**
	 * Sets the value of a given property with the value supplied. Also fires a
	 * property change if necessary.
	 * 
	 * @param id
	 *            Name of the parameter to be changed.
	 * @param value
	 *            Value to be set to the given parameter.
	 */
	public void setPropertyValue(Object id, Object value) {
		if (ID_SIZE.equals(id)) {
			Dimension s = getSize();
			value = new Dimension(s);
		}
		PropertyHandler propertyHandler = getPropertyHandler();
		if (propertyHandler != null)
			propertyHandler.setPropertyValue(id + "", value);
	}

	public abstract Object clone();

	public abstract PropertyHandler getPropertyHandler();

	public Object getEditableValue() {
		return this;
	}

	final Object getPropertyValue(String propName) {
		return null;
	}

	public boolean isPropertySet(Object propName) {
		return isPropertySet((String) propName);
	}

	final boolean isPropertySet(String propName) {
		return true;
	}

	public void resetPropertyValue(Object propName) {
	}

	final void resetPropertyValue(String propName) {
	}

	final void setPropertyValue(String propName, Object val) {
	}

}
